#region Using Statements
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace SkyBurner
{
    #region Particle class
    public class Particle
    {
        #region Properties
        private Vector3 m_vPosition;
        public Vector3 Position
        {
            get { return m_vPosition; }
        }

        private Vector3 m_vDirection;
        public Vector3 Direction
        {
            get { return m_vDirection; }
        }

        private float m_fLife;
        public float Life
        {
            get { return m_fLife; }
            set { m_fLife = value; }
        }

        private float m_fSpeed;
        public float Speed
        {
            get { return m_fSpeed; }
            set { m_fSpeed = value; }
        }

        private float m_fSize;
        public float Size
        {
            get { return m_fSize; }
            set { m_fSize = value; }
        }

        private Vector4 m_particleColor;
        public Vector4 Color
        {
            get { return m_particleColor; }
            set { m_particleColor = value; }
        }

        private float m_fAlphaFactor;
        public float AlphaFactor
        {
            get { return m_fAlphaFactor; }
            set { m_fAlphaFactor = value; }
        }
        #endregion

        public Particle(Vector3 vPosition, Vector3 vDirection, float fLife, float fSpeed, float fSize, Vector4 vColor)
        {
            m_vPosition = vPosition;
            m_vDirection = vDirection;
            m_fLife = fLife;
            m_fSpeed = fSpeed;
            m_fSize = fSize;
            m_particleColor = vColor;
            m_fAlphaFactor = 1.0f;
        }

        public void Update(GameTime gameTime)
        {
            m_fLife -= gameTime.ElapsedGameTime.Milliseconds;
            m_vPosition += (m_vDirection * (m_fSpeed * gameTime.ElapsedGameTime.Milliseconds * 0.001f));
        }
    }
    #endregion

    #region ParticleEmitterBase class
    public abstract class ParticleEmitterBase : BaseDrawableObject
    {
        #region Private members
        private Collection<Particle> m_ParticleList = new Collection<Particle>();
        private float m_fLastUpdateTick;
        private MOODParticleShader m_moodParticleShader;
        private PointSpriteShader m_pointSpriteShader;
        private int m_nParticleSpawned;
        protected ParticleEmitterConfig m_currConfig;
        protected float m_fLifeTime;
        #endregion

        #region Properties
        private bool m_bDead;
        public bool Dead
        {
            get { return m_bDead; }
        }

        private bool m_bActive;
        public bool Active
        {
            get { return m_bActive; }
            set { m_bActive = value; }
        }

        private Vector3 m_vPosition;        
        public Vector3 Position
        {
            get { return m_vPosition; }
            set { m_vPosition = value; }
        }

        private Texture2D m_particleTexture;
        public Texture2D ParticleTexture
        {
            get { return m_particleTexture; }
        }
        #endregion

        public ParticleEmitterBase()
        {

        }

        public void Initialize(BaseGame pGame, ParticleEmitterConfig pConfig)
        {
            m_Game = pGame;
            m_fLastUpdateTick = 0.0f;
            m_bDead = false;
            m_bActive = true;
            m_currConfig = pConfig;
            m_nParticleSpawned = 0;
            m_fLifeTime = 0.0f;

            m_particleTexture = m_Game.ContentManager.Load<Texture2D>(m_currConfig.ParticleTextureName);

            m_moodParticleShader = (MOODParticleShader)m_Game.ShadersManager.GetShader("ParticleShader");
            m_pointSpriteShader = (PointSpriteShader)m_Game.ShadersManager.GetShader("PointSpriteShader");
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime, Camera pActiveCamera)
        {
            int i = 0;
            while (i < m_ParticleList.Count)
            {
                if (m_ParticleList[i].Life > 0.0f)
                    m_ParticleList[i].Update(gameTime);
                else
                {
                    m_ParticleList.RemoveAt(i);
                    continue;
                }
                i++;
            }

            // increment the age of this emitter...
            m_fLifeTime += (float)gameTime.ElapsedGameTime.Milliseconds;
            if ((m_currConfig.KillAfter > 0.0f) && (m_fLifeTime > m_currConfig.KillAfter))
                m_bActive = false;

            if (m_bActive)
            {
                if ((m_ParticleList.Count < m_currConfig.MaxParticleCount) || m_currConfig.Loop)
                {
                    if ((gameTime.TotalGameTime.TotalMilliseconds - m_fLastUpdateTick) > m_currConfig.ParticleEmitInterval)
                    {
                        m_fLastUpdateTick = (float)gameTime.TotalGameTime.TotalMilliseconds;

                        for (i = 0; i < m_currConfig.ParticlePerFrame; i++)
                        {
                            if ((m_currConfig.MaxParticleCount > 0) && (m_ParticleList.Count < m_currConfig.MaxParticleCount))
                            {
                                m_nParticleSpawned++;
                                m_ParticleList.Add(OnCreateParticle(m_ParticleList.Count, m_ParticleList.Count));
                            }
                        }

                        if (m_nParticleSpawned >= m_currConfig.MaxParticleCount)
                            m_bActive = false;
                    }
                }
            }
            else
                m_bDead = (m_ParticleList.Count == 0);
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {
#if false
            m_Game.GraphicsDevice.RenderState.CullMode = CullMode.None;

            m_Game.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            m_Game.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            m_Game.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            m_Game.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            m_Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

            //m_Game.GraphicsDevice.RenderState.AlphaTestEnable = true;
            //m_Game.GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Greater;
            //m_Game.GraphicsDevice.RenderState.ReferenceAlpha = 0x0F;

            for (int i = m_ParticleList.Count - 1; i >= 0; i--)
            {
                Particle particle = m_ParticleList[i];

                particle.AlphaFactor = particle.Life / m_currConfig.ParticleLife;
                if (OnBeforeDrawParticle(m_ParticleList.Count, i, particle))
                    m_moodParticleShader.Draw(gameTime, pActiveCamera, this, particle);
            }

            m_Game.GraphicsDevice.RenderState.AlphaTestEnable = false;
            m_Game.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
#else
            // Enable point sprites.
            m_Game.GraphicsDevice.RenderState.PointSpriteEnable = true;
            m_Game.GraphicsDevice.RenderState.PointSizeMax = 256;

            // Set the alpha blend mode.
            m_Game.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            m_Game.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            m_Game.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            m_Game.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            // Set the alpha test mode.
            m_Game.GraphicsDevice.RenderState.AlphaTestEnable = true;
            m_Game.GraphicsDevice.RenderState.AlphaFunction = CompareFunction.Greater;
            m_Game.GraphicsDevice.RenderState.ReferenceAlpha = 0;

            // Enable the depth buffer (so particles will not be visible through
            // solid objects like the ground plane), but disable depth writes
            // (so particles will not obscure other particles).
            m_Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

            for (int i = m_ParticleList.Count - 1; i >= 0; i--)
            {
                Particle particle = m_ParticleList[i];

                particle.AlphaFactor = particle.Life / m_currConfig.ParticleLife;
                if (OnBeforeDrawParticle(m_ParticleList.Count, i, particle))
                    m_pointSpriteShader.Draw(gameTime, pActiveCamera, this, particle);
            }

            m_Game.GraphicsDevice.RenderState.PointSpriteEnable = false;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
#endif

            base.Draw(gameTime, pActiveCamera);
        }

        abstract protected Particle OnCreateParticle(int nParticleCount, int nParticleIndex);
        abstract protected bool OnBeforeDrawParticle(int nParticleCount, int nParticleIndex, Particle pParticleToDraw);
    }
    #endregion
}
